package cn.terenas.tool.excel.config;

import cn.terenas.tool.excel.common.constant.Constant;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.commons.lang3.tuple.Pair;

import java.io.File;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

@Slf4j
public class Config {

    private static final File CONFIG_FILE = new File("./excel-tool.conf");


    public static void loadConfig() {

        if (CONFIG_FILE.exists()) {
            try {
                log.info(">>> 检测到配置文件...");
                log.info(">>> 正在加载配置文件...");
                Map<String, String> loadedConfig = Optional.of(FileUtils.readLines(CONFIG_FILE, StandardCharsets.UTF_8))
                        .orElse(Lists.newArrayList())
                        .stream()
                        .filter(StringUtils::isNotBlank)
                        .filter(line -> !line.startsWith("#"))
                        .peek(line -> {
                            if (!line.startsWith(Constant.CONFIG_KEY_PREFIX)
                                    && line.contains("=") && line.indexOf("=") == line.lastIndexOf("=")) {
                                log.error(">>> 未知配置项:{}", line);
                                throw new RuntimeException("检测到未知配置项, 请检查配置文件!");
                            }
                        })
                        .map(line -> new MutablePair<>(line.split("=")[0], line.split("=")[1]))
                        .collect(Collectors.toMap(Pair::getLeft, Pair::getRight, (o1, o2) -> o1));

                loadCodeMapping(loadedConfig.get(Constant.CONFIG_KEY_CODE_MAPPING));
                loadDomesticAirport(loadedConfig.get(Constant.CONFIG_KEY_DOMESTIC_AIRPORT_CODE));
                loadInternationalAirport(loadedConfig.get(Constant.CONFIG_KEY_INTERNATIONAL_AIRPORT_CODE));
                loadUnionAirport(loadedConfig.get(Constant.CONFIG_KEY_UNION_CODE));

            } catch (Exception e) {
                log.debug(">>> 加载配置异常:", e);
                log.error(">>> 加载配置异常, {}", e.getMessage());
                throw new RuntimeException("加载配置异常, 请联系管理员!");
            }
        } else {
            log.info(">>> 未检测到配置文件, 初始化默认配置...");

            writeLine("# 机场编码映射关系, 映射关系之间逗号分隔, 机场编码与机场名称之间以-分隔");
            writeLine(Constant.CONFIG_KEY_CODE_MAPPING + "=" + getCodeMappingStr() + "\n");

            writeLine("# 国内机场编码, 三位字符 逗号分隔");
            writeLine(
                    Constant.CONFIG_KEY_DOMESTIC_AIRPORT_CODE + "="
                            + String.join(",", Constant.DOMESTIC_AIRPORT_CODE_SET) + "\n"
            );

            writeLine("# 国际机场编码, 三位字符, 逗号分隔");
            writeLine(
                    Constant.CONFIG_KEY_INTERNATIONAL_AIRPORT_CODE + "="
                            + String.join(",", Constant.INTERNATIONAL_AIRPORT_CODE_SET) + "\n"
            );

            writeLine("# 联盟编码, 两位字符, 逗号分隔");
            writeLine(Constant.CONFIG_KEY_UNION_CODE + "=" + String.join(",", Constant.UNION_CODE_SET) + "\n");

            writeLine("# 数据行数限制");
            writeLine(Constant.CONFIG_KEY_ROW_LIMIT + "=10000\n");
        }

        validConfig();

        log.info(">>> 配置加载完成.\n\n\n");
    }

    private static String getCodeMappingStr() {
        StringBuilder codeMapping = new StringBuilder();
        Constant.CODE_MAPPING.entrySet()
                .stream()
                .map(entry -> entry.getKey() + "-" + entry.getValue() + ",")
                .forEach(codeMapping::append);
        return codeMapping.toString().substring(0, codeMapping.length() - 1);
    }

    private static void validConfig() {
        Constant.CODE_MAPPING.keySet().forEach(code -> {
            if (StringUtils.isBlank(code) || code.length() > Constant.AIRPORT_CODE_LENGTH) {
                throw new RuntimeException("机场编码映射关系格式错误:" + code);
            }
        });

        Constant.DOMESTIC_AIRPORT_CODE_SET.forEach(code -> {
            if (StringUtils.isBlank(code) || code.length() > Constant.AIRPORT_CODE_LENGTH) {
                throw new RuntimeException("国内机场编码格式错误:" + code);
            }
        });

        Constant.INTERNATIONAL_AIRPORT_CODE_SET.forEach(code -> {
            if (StringUtils.isBlank(code) || code.length() > Constant.AIRPORT_CODE_LENGTH) {
                throw new RuntimeException("国际机场编码格式错误:" + code);
            }
        });

        boolean hasSameCode = CollectionUtils.containsAny(
                Constant.DOMESTIC_AIRPORT_CODE_SET, Constant.INTERNATIONAL_AIRPORT_CODE_SET
        );
        if (hasSameCode) {
            Constant.DOMESTIC_AIRPORT_CODE_SET.retainAll(Constant.INTERNATIONAL_AIRPORT_CODE_SET);
            throw new RuntimeException("国际机场编码与国内编码重复:" + Constant.DOMESTIC_AIRPORT_CODE_SET.toString());
        }


        Constant.UNION_CODE_SET.forEach(code -> {
            if (StringUtils.isBlank(code) || code.length() > Constant.UNION_CODE_LENGTH) {
                throw new RuntimeException("联盟编码格式错误:" + code);
            }
        });
    }

    private static void writeLine(String config) {
        try {
            FileUtils.write(CONFIG_FILE, config + "\n", StandardCharsets.UTF_8, true);
        } catch (Exception e) {
            log.debug(">>> 配置写入错误, e:", e);
            log.error(">>> 配置写入错误, 请联系管理员!");
            throw new RuntimeException("配置写入错误!");
        }
    }

    private static void loadCodeMapping(String value) {
        if (StringUtils.isNotEmpty(value)) {
            log.info(">>> 检测到机场编码映射关系配置:{}", value);
            Constant.CODE_MAPPING.clear();
            Constant.CODE_MAPPING.putAll(
                    Lists.newArrayList(value.split(",")).stream()
                            .map(mappingStr -> {
                                int index = mappingStr.indexOf("-");
                                return new MutablePair<>(mappingStr.substring(0, index), mappingStr.substring(index + 1));
                            })
                            .collect(
                                    Collectors.toMap(
                                            Pair::getLeft, Pair::getRight,
                                            (o1, o2) -> {
                                                throw new RuntimeException("\"" + o1 + "\"与\"" + o2 + "\"编码重复!");
                                            }
                                    )
                            )
            );

        } else {
            log.info(">>> 未检测到机场编码映射关系配置, 使用默认配置:{}", getCodeMappingStr());
        }
    }

    private static void loadDomesticAirport(String value) {
        if (StringUtils.isNotEmpty(value)) {
            log.info(">>> 检测到国内机场编码配置:{}", value);
            Constant.DOMESTIC_AIRPORT_CODE_SET.clear();
            Constant.DOMESTIC_AIRPORT_CODE_SET.addAll(Sets.newHashSet(value.split(",")));
        } else {
            log.info(">>> 未检测到国内机场编码配置, 使用默认配置:{}", String.join(",", Constant.DOMESTIC_AIRPORT_CODE_SET));
        }
    }

    private static void loadInternationalAirport(String value) {
        if (StringUtils.isNotEmpty(value)) {
            log.info(">>> 检测到国际机场编码配置:{}", value);
            Constant.INTERNATIONAL_AIRPORT_CODE_SET.clear();
            Constant.INTERNATIONAL_AIRPORT_CODE_SET.addAll(Sets.newHashSet(value.split(",")));
        } else {
            log.info(">>> 未检测到国际机场编码配置, 使用默认配置:{}", String.join(",", Constant.INTERNATIONAL_AIRPORT_CODE_SET));
        }
    }

    private static void loadUnionAirport(String value) {
        if (StringUtils.isNotEmpty(value)) {
            log.info(">>> 检测到联盟编码配置:{}", value);
            Constant.UNION_CODE_SET.clear();
            Constant.UNION_CODE_SET.addAll(Sets.newHashSet(value.split(",")));
        } else {
            log.info(">>> 未检测到联盟编码配置, 使用默认配置:{}", String.join(",", Constant.UNION_CODE_SET));
        }
    }


    public static boolean isDomesticAirport(String airportCode) {
        return Constant.DOMESTIC_AIRPORT_CODE_SET.contains(airportCode.toUpperCase());
    }

    public static boolean isInternationalAirport(String airportCode) {
        return Constant.INTERNATIONAL_AIRPORT_CODE_SET.contains(airportCode.toUpperCase());
    }

    public static boolean isUnionFlight(String flightCode) {
        return Constant.UNION_CODE_SET.contains(flightCode.substring(0, 2).toUpperCase());
    }

}
